Skip to content

Restore handling of 204 "soft" redirects on data requests #13364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 23, 2025

Conversation

brophdawg11
Copy link
Contributor

@brophdawg11 brophdawg11 commented Apr 4, 2025

tl;dr;

  • Single fetch .data requests now understand the 204 redirects we used in Remix v2 before Single Fetch
  • This allows folks to redirect from outside of RR - like an express middleware
  • Using these relies on implementation details (the header names) and is subject to break on a no-major release (we could change the header names, but it's highly unlikely we'd stop supporting the 204)
  • The recommendation for doing this "correctly" is to move the logic to a Root route middleware and use redirect

Background:

In order to trigger a redirect navigation from a client side fetch, we have to use something other than a 3xx response because a 3xx will redirect the fetch call. We need to return some form of "data" that we can look at and decide to perform a soft application redirect.

Prior to single fetch, we used to return 204 responses with X-Remix-Redirect headers. These worked well and used web standards so while the 204 code and custom header were "implementation details", folks could use them if/when needed to perform redirects on _data requests from outside of Remix - i.e., in an express middleware.

With single fetch, we switched to encoding the redirect into the body of the response so we could encode them in prerendered .data files as well. This means that a redirect on a .data request relies on the implementation detila of turbo-stream's encoding format.

This made redirects on .data requests from outside of React Router much tricker, since they now need to encode a turbo-stream response body and use the custom symbol we use internally (#12693).

Options: We have 2 options if we want to re-enable this functionality:

  • Expose a helper function that encodes a redirect via turbo-stream
    • ❌ I think this is a non-starter because when we move to the RSC format the utility won't be be future flag aware so it won't know the proper way to encode the body
  • ✅ Add back support for the 204 responses

This PR adds back support for those 204 responses so folks can redirect the same way they did in Remix v2.

⚠️ This approach means they are using implementation details that can change in semver patch/minor releases and it's up to the application to ensure they are validating their 204 redirects when updating to a new RR version.

Closes #12693

Copy link

changeset-bot bot commented Apr 4, 2025

🦋 Changeset detected

Latest commit: 748bf13

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
react-router Patch
@react-router/architect Patch
@react-router/cloudflare Patch
@react-router/dev Patch
react-router-dom Patch
@react-router/express Patch
@react-router/node Patch
@react-router/serve Patch
@react-router/fs-routes Patch
@react-router/remix-routes-option-adapter Patch
create-react-router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines 1487 to 1491
res.statusMessage = response.statusText;
res.status(response.status);
for (let [key, value] of response.headers.entries()) {
res.append(key, value);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't export our internal logic for converting a fetch Response to an express res so for now it's assumed that the application has to do that adapter logic on their own. I think this is ok since status/headers are all that matter? There isn't a body on these responses

} else {
return { status: SINGLE_FETCH_REDIRECT_STATUS, data };
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get back a 204 redirect we convert it to a single fetch redirect here for downstream processing

}
if (result.replace) {
headers["X-Remix-Replace"] = "yes";
headers["X-Remix-Replace"] = "true";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Align these with values used elsewhere - value doesn't really matter since we use .has(header)

@brophdawg11 brophdawg11 added the review flag for team review label Apr 9, 2025
@brophdawg11 brophdawg11 changed the title Add a new dataRedirect utility for external redirects on .data requests Restore handling of 204 "soft" redirects on data requests Apr 23, 2025
@alexanderson1993
Copy link
Contributor

I'm a fan.

Will this be documented unstable behavior? What's the motivation for not stabilizing the X-Remix-Redirect header as a proper API? Aside from, you know, having "Remix" in the name.

@brophdawg11
Copy link
Contributor Author

Will this be documented unstable behavior?

Not likely - the goal is parity with pre-single fetch to ease upgrading. It was undocumented/unstable before, and will be undocumented/unstable here as well. That said - these things are highly unlikely to change and it should be relatively easy to add an integration test that would fail if we did change a header name or something.

What's the motivation for not stabilizing the X-Remix-Redirect header as a proper API?

Now that you can do these types of "before the handlers run" redirects from a RR middleware, I don't know that we want to further encourage doing this outside of the app

// client side router. We use a 202 to avoid any automatic caching we might
// get from a 200 since a "temporary" redirect should not be cached. This lets
// the user control cache behavior via Cache-Control
export const SINGLE_FETCH_REDIRECT_STATUS = 202;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulled into here from the server-runtime file to avoid circular deps

@brophdawg11 brophdawg11 merged commit 8e4963f into dev Apr 23, 2025
8 checks passed
@brophdawg11 brophdawg11 deleted the brophdawg11/single-fetch-redirects branch April 23, 2025 20:44
@brophdawg11 brophdawg11 removed the review flag for team review label Apr 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants